package com.hzb.LNReader.convertor;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

public class HtmlParser {

	public HtmlHandler handler;
	
	public HtmlParser(HtmlHandler handler) throws Exception {
		this(handler, "GBK");
	}
	
	public HtmlParser(HtmlHandler handler, String encoding) throws Exception {
		this.handler = handler;
		setEncoding(encoding);
	}
	
	InputStream in = null;
	byte[] buffer;
	int bufferEnd;
	static final int bufferSize = 256 * 1024;
	
	static final byte st = (byte) '<',
			et = (byte) '>',
			eq = (byte) '=',
			qu = (byte) '\"',
			star = (byte) '*',
			m0 = (byte) '!',
			m1 = (byte) '-',
			splash = (byte) '/';
	
	private int fillBuffer(int head) throws IOException {
		int size;
		if (head > bufferEnd) size = head - bufferEnd;
		else {
			size = bufferSize - bufferEnd;
			if (head == 0) size--;
		}
		if (size == 0) return 0;
		bufferEnd += (size = in.read(buffer, bufferEnd, size));
		if (bufferEnd >= bufferSize) bufferEnd -= bufferSize;
		return size;
	}
	
	public void setEncoding(String encoding) throws Exception {
		this.encoding = encoding;
		handler.onEncodingChange(encoding);
	}
	
	private String encoding = "GBK";
	private HashMap<String, String> attrMap = new HashMap<String, String>();
	
	/**
	 * 结尾不包含
	 * @param start
	 * @param end 不包含
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	String newString(int start, int end) throws UnsupportedEncodingException {
		if (end > start) {
			return new String(buffer, start, end - start, encoding);
		} else {
			return new String(buffer, start, bufferSize - start, encoding) +
					new String(buffer, 0, end, encoding);
		}
	}
	String readToken() throws UnsupportedEncodingException {
		int kHead = -1, kEnd = -1;
		int i;
		for (i=iterHead; i!=iterEnd; i++) {
			if (i == bufferSize) i = 0;
			if (kHead == -1) {
//				遇到有效字符前
				if (Character.isWhitespace(buffer[i])) {
//					还是遇到空格
				} else {
//					遇到首个有效字符
					kHead = i;
				}
			} else {
//				有效字符中
				if (Character.isWhitespace(buffer[i])) {
//					遇到结束空白字符
					kEnd = i;
					break;
				} else {
//					遇到中间有效字符
				}
			}
		}
		iterHead = i;
		if (kHead == -1) return null;
		if (kEnd == -1) return newString(kHead, i);
		return newString(kHead, kEnd);
	}
	
	String readKey() throws UnsupportedEncodingException {
		int kHead = -1, kEnd = -1;
		boolean inSp = true;
		int i;
		for (i=iterHead; i!=iterEnd; i++) {
			if (i == bufferSize) i = 0;
			if (Character.isWhitespace(buffer[i])) {
//				遇到空格
				if (inSp) {
//					遇到连续的空格
				} else {
//					第一次遇到空格
					if (kHead != -1) kEnd = i;
					inSp = true;
				}
			} else {
//				遇到非空格
				if (buffer[i] == eq) {
//					遇到=
					if (kHead == -1) {
//						=前没有有效key
						
					} else {
//						有有效key
						if (kEnd == -1) kEnd = i; 
						break;
					}
				} else {
//					遇到非=
					if (inSp) {
//						遇到第一个有效字符
						kHead = i;
						kEnd = -1;
						inSp = false;
					} else {
//						遇到有效字符中间的东西
					}
				}
				inSp = false;
			}
		}
		iterHead = i;
		if (kHead == -1) return null;
		if (kEnd != -1) {
			iterHead++;
			return newString(kHead, kEnd);
		} else {
			return newString(kHead, i);
		}
	}
	
	String readValue() throws UnsupportedEncodingException {
		int kHead = -1, kEnd = -1;
		boolean inQu = false;
		int i;
		for (i=iterHead; i!=iterEnd; i++) {
			if (i == bufferSize) i = 0;
			if (!inQu) {
//				在value开始前
				if (buffer[i] == qu) {
//					遇到第一个"
					inQu = true;
					kHead = i;
				} else if (Character.isWhitespace(buffer[i])){
//					前缀空白字符
				} else {
//					遇到不正确value
					break;
				}
			} else {
//				在value开始后
				if (buffer[i] == qu) {
//					遇到value终结
					kEnd = i;
					break;
				} else {
//					遇到value中间字符
				}
			}
		}
		iterHead = i;
		if (kEnd != -1) {
			iterHead++;
			String ret = newString(kHead, kEnd);
			return ret.substring(1);
		}
		return null;
	}
	
	int iterHead, iterEnd;
	/**
	 * 解析一个Element的标记
	 * @param start 开始下标，'<'下一个位置
	 * @param end 结束下标,'>'所在位置
	 * @throws Exception 
	 */
	void parseElement(int start, int end) throws Exception {
		iterHead = start;
		iterEnd = end;
		
		String tag = readToken();
		if (tag == null) return ;
		if (tag.charAt(0) == splash) {
			tag = tag.substring(1).trim();
			if (tag.length() != 0 && !noEndTag.contains(tag)) handler.endElement(tag);
		} else {
			attrMap.clear();
			boolean selfEnd = false;
			for (;;) {
				String key = readKey();
				if (key == null) break;
				if ("/".equals(key)) {
					if (iterHead == iterEnd) {
						selfEnd = true;
						break;
					}
					else continue;
				}
				String value = readValue();
				if (value != null) {
					attrMap.put(key.toLowerCase(), value);
				}
			}
			handler.startElement(tag, attrMap);
			if (selfEnd || noEndTag.contains(tag)) handler.endElement(tag);
		}
	}
	
	static final HashSet<String> noEndTag = new HashSet<String> (Arrays.asList("img", "br", "link", "meta"));
	
	public void parse(InputStream in) throws Exception {
		this.in = in;
		buffer = new byte[bufferSize];
		try {
			doParse();
		} finally {
			buffer = null;
			attrMap = null;
			in = null;
			handler.finish();
		}
	}
	private void doParse() throws IOException, Exception {
		int tBegin = -1;
		int nextParse = 0;
		boolean inIgnore = false;
		int pendingLen = 0;
		boolean ignoreMatch = false;
		
		for (int i=0; ;i++) {
			if (i == bufferSize) i = 0;
			if (i == bufferEnd) {
				if (0 >= fillBuffer(i)) break;
			}
			if (inIgnore) {
//				忽略模式中
				switch (pendingLen) {
				case 0:
				case 1:
					if (buffer[i] == m1) pendingLen++;
					else pendingLen = 0;
					break;
				case 2:
					if (buffer[i] == et) {
						// 遇到忽略结尾
						inIgnore = false;
						if ((nextParse = i + 1) == bufferSize) nextParse = 0;
					}
					break;
				}
			} else {
//				非忽略模式中
				if (ignoreMatch) {
					pendingLen ++;
					switch (pendingLen) {
					case 2:
						if (buffer[i] != m0) ignoreMatch = false;
						break;
					case 3:
						if (buffer[i] != m1) ignoreMatch = false;
						break;
					case 4:
						ignoreMatch = false;
						if (buffer[i] == m1) {
							inIgnore = true;
							pendingLen = 0;
							continue;
						}
						break;
					}
				}
				if (buffer[i] == st) {
//					遇到'<'
					if (i > nextParse) handler.characters(buffer, nextParse, i - nextParse);
					else if (i < nextParse) {
						handler.characters(buffer, nextParse, bufferSize - nextParse);
						handler.characters(buffer, 0, i);
					}
					nextParse = tBegin = i;
					pendingLen = 1;
					ignoreMatch = true;
				} else if (buffer[i] == et) {
//					遇到'>'
					if (tBegin != -1) {
//						前面有正常的'<'
						handler.endCharacters();
						if ((tBegin += 1) == bufferSize) {
							parseElement(0, i);
						} else {
							parseElement(tBegin, i);
						}
						tBegin = -1;
						if ((nextParse = i + 1) == bufferSize) nextParse = 0;
						pendingLen = 0;
					}
				} else {
//					遇到非'<','>'
				}
			}
		}
	}
	
	public static void main(String[] args) throws Exception {
		new HtmlParser(new HtmlToTxtConvertor(new File("d:\\1.lnd"))).parse(new FileInputStream(new File("d:\\1.htm")));
	}
}
